package com.seewo.dubbo.debugger.admin.service.impl;


import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ReferenceConfig;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.seewo.dubbo.debugger.admin.aop.Debug;
import com.seewo.dubbo.debugger.admin.common.Constants;
import com.seewo.dubbo.debugger.admin.entity.Method;
import com.seewo.dubbo.debugger.admin.entity.Parameter;
import com.seewo.dubbo.debugger.admin.exception.DubboDebuggerException;
import com.seewo.dubbo.debugger.admin.mapper.MethodMapper;
import com.seewo.dubbo.debugger.admin.mapper.ServiceMapper;
import com.seewo.dubbo.debugger.admin.po.MethodInterfacePO;
import com.seewo.dubbo.debugger.admin.query.DebugQuery;
import com.seewo.dubbo.debugger.admin.service.IMethodService;
import com.seewo.dubbo.debugger.admin.service.IParameterService;
import com.seewo.dubbo.debugger.admin.utils.EhcacheTemplate;
import com.seewo.dubbo.debugger.api.ApiDebuggerService;
import com.seewo.dubbo.debugger.api.Arg;
import com.seewo.dubbo.debugger.api.Request;
import com.seewo.dubbo.debugger.api.Result;
import com.seewo.dubbo.debugger.api.model.ApiDef;
import com.seewo.dubbo.debugger.api.model.ModelDef;
import com.seewo.dubbo.debugger.api.model.ParamDef;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author chenliangliang
 * @since 2018-07-25
 */
@Slf4j
@Service
public class MethodServiceImpl extends ServiceImpl<MethodMapper, Method> implements IMethodService {


    @Autowired
    private EhcacheTemplate ehcacheTemplate;

    @Autowired
    private ServiceMapper serviceMapper;

    @Autowired
    private IParameterService parameterService;

    @Autowired
    private ThreadPoolTaskExecutor executor;

    @Override
    public MethodMapper getMapper() {
        return this.baseMapper;
    }


    @Override
    public Method getApi(Method method) {
        final Integer interfaceId = method.getCInterfaceId();
        final String methodName = method.getCName();
        MethodInterfacePO methodInterfacePo = getMapper().selectMethodInterface(interfaceId, methodName);
        if (methodInterfacePo == null) {
            throw new DubboDebuggerException("this method-" + method + " not exist!");
        }
        //缓存数据10分钟
        return ehcacheTemplate.cacheable(Constants.CACHE_NAME_METHOD_API, interfaceId + methodName, () -> getApi0(method, methodInterfacePo));
    }

    private Method getApi0(Method method, MethodInterfacePO methodInterfacePo) {
        String interfaceName = methodInterfacePo.getInterfaceName();
        final Integer methodId = methodInterfacePo.getMethodId();
        ApiDebuggerService apiDebuggerService = getApiDebuggerService(methodInterfacePo.getServiceId());
        ApiDef api = apiDebuggerService.getApi(interfaceName, method.getCName());

        method.setCDesc(methodInterfacePo.getMethodDesc());
        method.setCId(methodId);

        List<ParamDef> requestParams = api.getRequestParams();
        ParamDef responseParam = api.getResponseParam();
        Set<String> nameSet = new HashSet<>();
        Set<String> typeSet = new HashSet<>();
        Map<ParamDef, Parameter> requestParameterMap = new HashMap<>(32);
        Map<ParamDef, Parameter> responseParameterMap = new HashMap<>(32);
        //给集合赋值
        assignment(requestParams, null, nameSet, typeSet, requestParameterMap, methodId);
        assignment(Collections.singletonList(responseParam), null, nameSet, typeSet, responseParameterMap, methodId);

        List<Parameter> parameterList = parameterService.selectList(new EntityWrapper<Parameter>()
                .eq("c_method_id", methodId)
                .in("c_name", nameSet)
                .in("c_type", typeSet));

        Map<Parameter, Parameter> parameterMap = new HashMap<>(64);
        Map<String, Map<String, Parameter>> modelMap = new HashMap<>(8);
        parameterList.forEach(p -> {
            String name = p.getCName();
            int i = name.lastIndexOf(".");
            if (i > 0) {
                String className = name.substring(0, i);
                Map<String, Parameter> modelParameterMap = modelMap.computeIfAbsent(className, k -> new HashMap<>());
                modelParameterMap.put(name.substring(i + 1), p);
            }
            parameterMap.put(p, p);
        });

        List<Parameter> insertList = new ArrayList<>();

        List<ParamDef> requestList = getRetParamList(requestParams, requestParameterMap, parameterMap, modelMap, insertList);
        List<ParamDef> responseList = getRetParamList(Collections.singletonList(responseParam), responseParameterMap, parameterMap, modelMap, insertList);

        //异步更新t_parameter表
        executor.execute(() -> updateTableParameter(insertList));

        method.setRequestParams(requestList);
        method.setResponseParam(responseList.get(0));

        return method;
    }

    private List<ParamDef> getRetParamList(List<ParamDef> retParams, Map<ParamDef, Parameter> retParameterMap, Map<Parameter, Parameter> parameterMap, Map<String, Map<String, Parameter>> modelMap, List<Parameter> insertList) {
        List<ParamDef> retList = new ArrayList<>();
        for (ParamDef paramDef : retParams) {
            Parameter parameter = retParameterMap.get(paramDef);
            if (parameterMap.containsKey(parameter)) {
                //合并结果
                Parameter p = parameterMap.get(parameter);
                paramDef.setId(p.getCId());
                paramDef.setDescription(p.getCDesc() == null ? "" : p.getCDesc());
                ModelDef modelDef = paramDef.getModelDef();
                if (modelDef != null) {
                    String className = modelDef.getClassName();
                    List<ParamDef> propertyList = modelDef.getPropertyList();
                    Map<String, Parameter> propertiesMap = modelMap.get(className);
                    if (propertiesMap == null) {
                        for (ParamDef pd : propertyList) {
                            Parameter param = retParameterMap.get(pd);
                            param.setCCreateTime(new Date());
                            param.setCUpdateTime(new Date());
                            insertList.add(param);
                        }
                    } else {
                        for (ParamDef pd : propertyList) {
                            Parameter param = propertiesMap.get(pd.getName());
                            pd.setDescription(param.getCDesc());
                        }
                    }
                }
            } else {
                parameter.setCCreateTime(new Date());
                parameter.setCUpdateTime(new Date());
                insertList.add(parameter);
            }
            retList.add(paramDef);
        }
        return retList;
    }

    private void updateTableParameter(List<Parameter> insertList) {
        int size = insertList.size();
        if (size > 0) {
            int res = parameterService.getMapper().batchInsert(insertList);
            if (res != size) {
                log.warn("批量更新parameter异常");
            }
        }
    }


    private void assignment(List<ParamDef> paramDefs, String prefix, Set<String> nameSet, Set<String> typeSet, Map<ParamDef, Parameter> parameterMap, Integer methodId) {
        if (paramDefs.isEmpty()) {
            return;
        }
        for (ParamDef paramDef : paramDefs) {
            paramDef.setMethodId(methodId);
            String type = paramDef.getType();
            typeSet.add(type);
            String paramName = paramDef.getName();
            paramName = prefix == null ? paramName : prefix + paramName;
            nameSet.add(paramName);
            Parameter parameter = new Parameter();
            parameter.setCMethodId(methodId);
            parameter.setCIndex(paramDef.getIndex());
            parameter.setCDesc(paramDef.getDescription());
            parameter.setCType(type);
            parameter.setCName(paramName);
            parameterMap.put(paramDef, parameter);
            ModelDef modelDef = paramDef.getModelDef();
            if (modelDef != null) {
                assignment(modelDef.getPropertyList(), modelDef.getClassName() + ".", nameSet, typeSet, parameterMap, methodId);
            }
        }
    }


    private ApiDebuggerService getApiDebuggerService(Integer serviceId) {
        return getApiDebuggerService(serviceId, "", "");
    }

    @Override
    public ApiDebuggerService getApiDebuggerService(Integer serviceId, final String address, final String protocol) {
        String key = Constants.CACHE_NAME_DEBUGGER_SERVICE + "_" + serviceId.toString();
        //缓存1小时
        return ehcacheTemplate.cacheable(Constants.CACHE_NAME_DEBUGGER_SERVICE, key, () -> {
            String addr = address, prot = protocol;
            if (StringUtils.isAnyEmpty(address, protocol)) {
                com.seewo.dubbo.debugger.admin.entity.Service service = serviceMapper.selectById(serviceId);
                addr = service.getCAddress();
                prot = service.getCProtocol();
            }
            ApplicationConfig applicationConfig = new ApplicationConfig("dubbo-debugger-admin");
            ReferenceConfig<ApiDebuggerService> referenceConfig = new ReferenceConfig<>();
            referenceConfig.setApplication(applicationConfig);
            String url = prot + "://" + addr + "/" + Constants.DUBBO_DEBUGGER_SERVICE;
            referenceConfig.setUrl(url);
            referenceConfig.setInterface(ApiDebuggerService.class);
            referenceConfig.setVersion("1.0.0");
            // dubbo官方提示：此实例很重，封装了与注册中心的连接以及与提供者的连接，请自行缓存，否则可能造成内存和连接泄漏
            return referenceConfig.get();
        });
    }

    @Override
    @Debug(methodId = "@{#debugQuery.methodId}", result = "@{#returnObject.flag}")
    public Result debug(DebugQuery debugQuery) {
        Integer methodId = debugQuery.getMethodId();
        boolean parse = debugQuery.isParse();
        MethodInterfacePO methodInterfacePo = getMapper().selectMethodAndInterfaceNameByMethodId(methodId);
        if (methodInterfacePo == null) {
            throw new DubboDebuggerException("方法-(id=" + methodId + ") 不存在");
        }
        //methodId、query
        String methodName = methodInterfacePo.getMethodName();
        methodName = methodName.substring(0, methodName.indexOf("("));
        Request request = new Request();
        request.setMethodName(methodName);
        request.setInterfaceName(methodInterfacePo.getInterfaceName());

        List<ParamDef> requestParams = debugQuery.getRequestParams();
        for (ParamDef paramDef : requestParams) {
            Arg arg = new Arg();
            arg.setIndex(paramDef.getIndex());
            arg.setName(paramDef.getName());
            arg.setType(paramDef.getType());
            ModelDef modelDef = paramDef.getModelDef();
            String value = paramDef.getValue();
            if (modelDef != null) {
                //对象
                if (parse) {
                    value = parse2Value(modelDef.getPropertyList());
                }
                arg.setClassName(modelDef.getClassName());
            }
            arg.setValue(value);
            request.addArg(arg);
        }
        ApiDebuggerService apiDebuggerService = getApiDebuggerService(methodInterfacePo.getServiceId());
        return apiDebuggerService.debug(request);
    }

    @Override
    @Debug(methodId = "@{#$1}", result = "@{#returnObject.flag}")
    public Result debug(Integer methodId, List<Arg> args) {
        MethodInterfacePO methodInterfacePo = getMapper().selectMethodAndInterfaceNameByMethodId(methodId);
        if (methodInterfacePo == null) {
            throw new DubboDebuggerException("方法-(id=" + methodId + ") 不存在");
        }
        Request request = new Request();
        request.setMethodName(methodInterfacePo.getMethodName());
        request.setInterfaceName(methodInterfacePo.getInterfaceName());
        request.addArgs(args);
        ApiDebuggerService apiDebuggerService = getApiDebuggerService(methodInterfacePo.getServiceId());
        return apiDebuggerService.debug(request);
    }

    @Override
    public void updateDesc(Method method) {
        //异步更新缓存策略：先更新数据库，再删除缓存
        boolean res = updateById(method);
        if (res) {
            executor.execute(() -> {
                Method met = selectById(method.getCId());
                String key = met.getCInterfaceId() + met.getCName();
                ehcacheTemplate.cacheEvict(Constants.CACHE_NAME_METHOD_API, key);
            });
        } else {
            throw new RuntimeException("更新描述失败");
        }
    }

    /**
     * 解析成json字符串
     *
     * @param properties
     * @return
     */
    private static String parse2Value(List<ParamDef> properties) {
        StringBuilder sb = new StringBuilder("{");
        for (ParamDef paramDef : properties) {
            sb.append("\"").append(paramDef.getName()).append("\":");
            ModelDef modelDef = paramDef.getModelDef();
            if (modelDef == null) {
                sb.append("\"").append(paramDef.getValue()).append("\"");
            } else {
                sb.append(parse2Value(modelDef.getPropertyList()));
            }
            sb.append(",");
        }
        if (sb.charAt(sb.length() - 1) == ',') {
            sb.deleteCharAt(sb.length() - 1);
        }
        sb.append("}");
        return sb.toString();
    }

}
